#include "ModelRender.h"

ModelRender::ModelRender(bool hasAnimation) {
    manager = RenderManager();
    
    
    isAnimated = hasAnimation;
    currentAnimation = 0;
    translate = vec3(0.0f);
    scale = vec3(1.0f);
    rotateAxis = vec3(1.0f, 0.0f, 0.0f);
    rotateAngle = 0.0f;

    assimpScene = NULL;
}


template<class T>
void ModelRender::GenerateBuffers(GLuint* handle, std::vector<T> data) {
    if (handle != NULL) {
        glGenBuffers(1, handle);
        glBindBuffer(GL_ARRAY_BUFFER, *handle);
        glBufferData(GL_ARRAY_BUFFER, sizeof(T)*data.size(), &data[0], GL_STREAM_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }
}

void ModelRender::Initialize(const std::string& filename) {
    assimpScene = assimpImporter.ReadFile(filename.c_str(), aiProcessPreset_TargetRealtime_Fast);

    if (assimpScene) {
        InitFromScene(assimpScene, filename);
    }
    else {
        printf("Error parsing '%s': '%s'\n", filename.c_str(), assimpImporter.GetErrorString());
    }
}

bool ModelRender::InitFromScene(const aiScene* pScene, const std::string& filename) {
    //m_Textures.resize(pScene->mNumMaterials);
    const aiVector3D Zero3D(-2.0f, -2.0f, 0.0f);
    int meshCount = pScene->mNumMeshes;
    
    int NumVertices = 0;
    int NumIndices = 0;

    // Count the number of vertices and indices
    for (int i = 0 ; i < meshCount; i++) {
        NumVertices += pScene->mMeshes[i]->mNumVertices;
        NumIndices += pScene->mMeshes[i]->mNumFaces * 3;
    }

    // Initialize the meshes in the model, one by one
    for (int i = 0; i < meshCount; i++) {
        std::vector<GLfloat> positions;
        std::vector<GLfloat> normals;
        std::vector<GLuint> indicies;
        std::vector<GLfloat> textureCoords;
        const aiMesh* mesh = pScene->mMeshes[i];
        MeshInfo newMesh;
        
        // Set the array data
        // Populate the vertex attribute vectors
        for (uint i = 0 ; i < mesh->mNumVertices; i++) {
            const aiVector3D* pPos = &(mesh->mVertices[i]);
            const aiVector3D* pNormal = &(mesh->mNormals[i]);
            const aiVector3D* pTexCoord = mesh->HasTextureCoords(0) ? &(mesh->mTextureCoords[0][i]) : &Zero3D;
            
            positions.push_back(pPos->x);
            positions.push_back(pPos->y);
            positions.push_back(pPos->z);
            list.push_back(vec3(pPos->x, pPos->y, pPos->z));

            normals.push_back(pNormal->x);
            normals.push_back(pNormal->y);
            normals.push_back(pNormal->z);
            
            float random1 = (float)rand();
            float random2 = (float)rand();
            vec3 random = vec3(random1 / RAND_MAX, random2 / RAND_MAX, 0.0f);
            
            if (mesh->HasTextureCoords(0)) {
               textureCoords.push_back(random.x);
               textureCoords.push_back(random.y);
            }
            else {
               textureCoords.push_back(pTexCoord->x);
               textureCoords.push_back(pTexCoord->y);
            }
        }

        // Populate the index buffer
        for (uint i = 0 ; i < mesh->mNumFaces ; i++) {
            const aiFace& currFace = mesh->mFaces[i];

            if (currFace.mNumIndices != 3) {
                //printf("Unknown face count: %d\n", currFace.mNumIndices);
            }            

            //assert(currFace.mNumIndices == 3);
            indicies.push_back(currFace.mIndices[0]);
            indicies.push_back(currFace.mIndices[1]);
            indicies.push_back(currFace.mIndices[2]);
        }
        
        // Generate buffers for each data type
        GenerateBuffers(&newMesh.normalBuffer, normals); 
        GenerateBuffers(&newMesh.indexBuffer, indicies); 
        GenerateBuffers(&newMesh.posBuffer, positions);
        GenerateBuffers(&newMesh.textureBuffer, textureCoords);
        newMesh.vertexCount = indicies.size();

        // Add the mesh to the list of meshes
        meshes.push_back(newMesh);

        // Clear the arrays
        normals.clear();
        indicies.clear();
        positions.clear();
        textureCoords.clear();
    }
    
    return true;
}

void ModelRender::UpdateAnimation(unsigned int animationIndex, float timeStep){
    const aiScene* scene = assimpScene;
    const aiAnimation* mAnim = scene->mAnimations[animationIndex];
  
    float currentTime = currentAnimationTime + timeStep;
    currentAnimationTime = currentTime;

    // calculate the transformations for each animation channel
    for (unsigned int a = 0; a < mAnim->mNumChannels; a++) {
        const aiNodeAnim* channel = mAnim->mChannels[a];
        aiNode* targetNode = scene->mRootNode->FindNode(channel->mNodeName);

        if (currentTime > mAnim->mDuration) {
            currentTime = currentTime - mAnim->mDuration;
            currentAnimationTime = currentTime;
        }
        
        // ******** Position *****
        aiVector3D presentPosition(0, 0, 0);
        if (channel->mNumPositionKeys > 0) {
            // Look for present frame number. Search from last position if time is after the last time, else from beginning
            // Should be much quicker than always looking from start for the average use case.
            unsigned int frame = 0;
            while (frame < channel->mNumPositionKeys - 1) {
                if (currentTime < channel->mPositionKeys[frame+1].mTime)
                    break;

                frame++;
            }
            
            // interpolate between this frame's value and next frame's value
            unsigned int nextFrame = (frame + 1) % channel->mNumPositionKeys;
            const aiVectorKey& key = channel->mPositionKeys[frame];
            const aiVectorKey& nextKey = channel->mPositionKeys[nextFrame];
            double diffTime = nextKey.mTime - key.mTime;

            if (diffTime < 0.0) {
                diffTime += mAnim->mDuration;
            }
            
            if (diffTime > 0) {
                float factor = float((currentTime - key.mTime) / diffTime);
                presentPosition = key.mValue + (nextKey.mValue - key.mValue) * factor;
            } 
            else {
                presentPosition = key.mValue;
            }
        }
        
        // ******** Rotation *********
        aiQuaternion presentRotation(1, 0, 0, 0);
        if (channel->mNumRotationKeys > 0) {
            unsigned int frame = 0;
            while (frame < channel->mNumRotationKeys - 1) {
                if (currentTime < channel->mRotationKeys[frame+1].mTime)
                    break;

                frame++;
            }
            
            // interpolate between this frame's value and next frame's value
            unsigned int nextFrame = (frame + 1) % channel->mNumRotationKeys;
            const aiQuatKey& key = channel->mRotationKeys[frame];
            const aiQuatKey& nextKey = channel->mRotationKeys[nextFrame];
            double diffTime = nextKey.mTime - key.mTime;
            
            if (diffTime < 0.0) {
                diffTime += mAnim->mDuration;
            }
        
            if (diffTime > 0) {
                float factor = float((currentTime - key.mTime) / diffTime);
                aiQuaternion::Interpolate(presentRotation, key.mValue, nextKey.mValue, factor);
            } 
            else {
                presentRotation = key.mValue;
            }
        }
        
        // ******** Scaling **********
        aiVector3D presentScaling(1, 1, 1);
        if (channel->mNumScalingKeys > 0) {
            unsigned int frame = 0;
            while (frame < channel->mNumScalingKeys - 1) {
                if (currentTime < channel->mScalingKeys[frame+1].mTime)
                    break;

                frame++;
            }
            
            // TODO: (thom) interpolation maybe? This time maybe even logarithmic, not linear
            presentScaling = channel->mScalingKeys[frame].mValue;
        }
        
        // build a transformation matrix from it
        aiMatrix4x4 mat = aiMatrix4x4(presentRotation.GetMatrix());
        mat.a1 *= presentScaling.x; mat.b1 *= presentScaling.x; mat.c1 *= presentScaling.x;
        mat.a2 *= presentScaling.y; mat.b2 *= presentScaling.y; mat.c2 *= presentScaling.y;
        mat.a3 *= presentScaling.z; mat.b3 *= presentScaling.z; mat.c3 *= presentScaling.z;
        mat.a4 = presentPosition.x; mat.b4 = presentPosition.y; mat.c4 = presentPosition.z;
        
        targetNode->mTransformation = mat;
    }
}

void ModelRender::UpdateVertices() {
    const aiScene* scene = assimpScene;

    // update mesh position for the animation
    for (unsigned int i = 0; i < meshes.size(); ++i){
        // current mesh we are introspecting
        const aiMesh* mesh = assimpScene->mMeshes[i];
        
        // calculate bone matrices
        std::vector<aiMatrix4x4> boneMatrices( mesh->mNumBones);
        for (unsigned int a = 0; a < mesh->mNumBones; ++a) {
            const aiBone* bone = mesh->mBones[a];
            
            // find the corresponding node by again looking recursively through the node hierarchy for the same name
            aiNode* node = scene->mRootNode->FindNode(bone->mName);
            
            // start with the mesh-to-bone matrix 
            boneMatrices[a] = bone->mOffsetMatrix;
            
            // and now append all node transformations down the parent chain until we're back at mesh coordinates again
            const aiNode* tempNode = node;
            while (tempNode) {
                // check your matrix multiplication order here!!!
                boneMatrices[a] = tempNode->mTransformation * boneMatrices[a];
                
                // boneMatrices[a] = boneMatrices[a] * tempNode->mTransformation;
                tempNode = tempNode->mParent;
            }
        }
        
        // all using the results from the previous code snippet
        std::vector<aiVector3D> resultPos(mesh->mNumVertices); 
        std::vector<aiVector3D> resultNorm(mesh->mNumVertices);
        
        // loop through all vertex weights of all bones
        for (unsigned int a = 0; a < mesh->mNumBones; a++) {
            const aiBone* bone = mesh->mBones[a];
            const aiMatrix4x4& posTrafo = boneMatrices[a];
            
            // 3x3 matrix, contains the bone matrix without the translation, only with rotation and possibly scaling
            aiMatrix3x3 normTrafo = aiMatrix3x3(posTrafo); 
            for (unsigned int b = 0; b < bone->mNumWeights; b++) {
                const aiVertexWeight& weight = bone->mWeights[b];
                
                size_t vertexId = weight.mVertexId; 
                const aiVector3D& srcPos = mesh->mVertices[vertexId];
                const aiVector3D& srcNorm = mesh->mNormals[vertexId];
                
                resultPos[vertexId] += weight.mWeight * (posTrafo * srcPos);
                resultNorm[vertexId] += weight.mWeight * (normTrafo * srcNorm);
            }
        }

        // Update the VBO's (TODO: move to gpu)
        std::vector<GLfloat> positions;
        std::vector<GLfloat> normals;
        
        //printf("Updating %d vertices\n", resultPos.size());

        for (int a = 0 ; a < resultPos.size(); a++) {
            positions.push_back(resultPos[a].x);
            positions.push_back(resultPos[a].y);
            positions.push_back(resultPos[a].z);

            normals.push_back(resultNorm[a].x);
            normals.push_back(resultNorm[a].y);
            normals.push_back(resultNorm[a].z);
        }

        GenerateBuffers(&meshes[i].normalBuffer, normals); 
        GenerateBuffers(&meshes[i].posBuffer, positions);    
    }
}

void ModelRender::Draw(float dt) {
   if (assimpScene->HasAnimations()) {
      UpdateAnimation(0, 0.016f);
      UpdateVertices();
   }
    
   glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	
   manager.SetColor(color);

   for (int i = 0; i < meshes.size(); i++) {
      manager.DrawMesh(meshes[i], translate, scale, rotateAxis, rotateAngle);
   }
    
   glDisable(GL_TEXTURE_2D);
}

std::vector<glm::vec3> ModelRender::GetVertices() {
   return list;
}

void ModelRender::Dispose() {

}
